/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package it.txt.access.capability.libraries.rabbitmq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.RpcClient;
import com.rabbitmq.client.ShutdownSignalException;

import it.txt.access.capability.commons.signer.X509DocumentSigner;
import it.txt.access.capability.commons.signer.model.X509CertificateKeyValues;
import it.txt.access.capability.commons.signer.model.X509CertificateSubjectInfo;
import it.txt.access.capability.commons.signer.model.X509DocumentSignerInfo;
import it.txt.access.capability.commons.utils.XMLPrinter;
import it.txt.access.capability.finder.CapabilitySearchReturn;
import it.txt.access.capability.finder.CapabilityXQuerySaxURISearch;
import it.txt.access.capability.libraries.rabbitmq.exception.AbstractApplicationException;
import it.txt.access.capability.libraries.rabbitmq.impl.publisher.PublisherAppModel;
import it.txt.access.capability.libraries.rabbitmq.impl.la_publisher.LAPublisherAppModel;
import it.txt.access.capability.libraries.rabbitmq.impl.subscriber.SubscriberAppModel;
import it.txt.access.capability.libraries.rabbitmq.impl.model.AbstractApplicationModel;
import it.txt.access.capability.services.ens.schema.RequestType;
import it.txt.access.capability.services.ens.schema.ResponseType;
import it.txt.access.capability.services.ens.schema.factory.ENSServiceFactoryException;
import it.txt.access.capability.services.ens.schema.request.factory.ENSServiceRequestFactory;
import it.txt.access.capability.services.ens.schema.response.factory.ENSServiceResponseFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;

/**
 *
 * @author seccia
 */
public class GuestApplication {

    protected static final Logger LOGGER = Logger.getLogger(GuestApplication.class.getName());
    private ENSServiceResponseFactory ensResponseFactory;
    private ENSServiceRequestFactory ensRequestFactory;
    private AbstractApplicationModel appModel;
    private Connection rabbitConnection;
    private ConnectionFactory factory;
    private boolean isGuestListening;
    private Channel rabbitChannel;

    public GuestApplication(AbstractApplicationModel appModel) {
        this.appModel = appModel;
    }

    public ResponseType newGuestTransaction() throws AbstractApplicationException {
        try {
            initENSFactoryClasses();
            openRabbitMQGuestConnection();
            Document document = createRPCClientSignedRequest();
            String response = sendRPCClientRequest(document);
            closeRabbitMQGuestConnection();
            return onRPCClientResponse(response);
        } catch (Exception e) {
            if (!isGuestSubscriberListening()) {
                LOGGER.info("Subscriber stopped");
            }
            throw new AbstractApplicationException(e.getMessage(), e);
        }
    }

    private void initENSFactoryClasses() throws AbstractApplicationException {
        try {
            String capabilitySchemasPath = "../../TXTAccess_BIN/Schemas";
            if (capabilitySchemasPath == null || capabilitySchemasPath.isEmpty()) {
                throw new AbstractApplicationException("Unable to get capability schemas path system property.");
            }
            LOGGER.info("Creating ENSServiceRequestFactory instance...");
            ensRequestFactory = ENSServiceRequestFactory.getInstance(capabilitySchemasPath);
            LOGGER.info("Creating ENSServiceResponseFactory instance...");
            ensResponseFactory = ENSServiceResponseFactory.getInstance(capabilitySchemasPath);
        } catch (ENSServiceFactoryException e) {
            throw new AbstractApplicationException(e.getMessage(), e);
        }
    }

    private void openRabbitMQGuestConnection() throws AbstractApplicationException {

        LOGGER.info("Creating RabbitMQ Guest ConnectionFactory...");
        LOGGER.log(Level.INFO, "Using User Name: {0}", appModel.getGuestCredentialUser());
        LOGGER.log(Level.INFO, "Using User Pwd: {0}", appModel.getGuestCredentialPwd());
        LOGGER.log(Level.INFO, "Using Broker Host: {0}", appModel.getGuestBrokerHost());
        LOGGER.log(Level.INFO, "Using Broker Port: {0}", appModel.getGuestBrokerPort());
        LOGGER.log(Level.INFO, "Using Broker V Hosh: {0}", appModel.getGuestBrokerVHost());
        LOGGER.log(Level.INFO, "Using Request Queue: {0}", appModel.getGuestENSAuthServiceMBX());
        factory = new ConnectionFactory();
        factory.setUsername(appModel.getGuestCredentialUser());
        factory.setPassword(new String(appModel.getGuestCredentialPwd()));
        factory.setPort(appModel.getGuestBrokerPort());
        factory.setHost(appModel.getGuestBrokerHost());
        factory.setVirtualHost(appModel.getGuestBrokerVHost());

        try {
            LOGGER.info("Creating RabbitMQ Guest Connection...");
            rabbitConnection = factory.newConnection();

            LOGGER.info("Creating RabbitMQ Guest Channel...");
            rabbitChannel = rabbitConnection.createChannel();

        } catch (IOException e) {
            throw new AbstractApplicationException(e.getMessage(), e);
        }
    }

    private Document createRPCClientSignedRequest()
            throws AbstractApplicationException {
        try {

            LOGGER.info("Creating ENSService Request object...");
            URI resourceID = null;
            RequestType requestType = null;
            if (appModel instanceof PublisherAppModel) {
                PublisherAppModel pubParams = (PublisherAppModel) appModel;
                resourceID = new URI(pubParams.getLeafNodeID());
            } else if (appModel instanceof LAPublisherAppModel) {
                LAPublisherAppModel pubParams = (LAPublisherAppModel) appModel;
                resourceID = new URI(pubParams.getLogAndAuditNodeID());
            } else {
                SubscriberAppModel subParams = (SubscriberAppModel) appModel;
                resourceID = new URI(subParams.getResourceid());
            }

            if (appModel.getCapabilityHashCode() != null && !appModel.getCapabilityHashCode().isEmpty()) {

                LOGGER.info("Creating ENSService Request using Capability HASH Code...");

                requestType = ensRequestFactory.createENSServiceRequest(resourceID,
                        appModel.getSignerInfo().getSignerID(),
                        appModel.getAbstractOperationName(),
                        appModel.getSessinDeadline(),
                        appModel.getCapabilityHashCode());
            }
            else {

                LOGGER.info("Creating ENSService Request - Searching for capabilities...");
                LOGGER.log(Level.INFO, "Searching - Using Subject ID: {0}", appModel.getSignerInfo().getSignerID());
                LOGGER.log(Level.INFO, "Searching - Using Resource ID: {0}", resourceID.toString());
                LOGGER.log(Level.INFO, "Searching - Using Operation: {0}", appModel.getAbstractOperationName());

                File capabilitiesFolder = new File(appModel.getCapabilitiesLocation());
                CapabilityXQuerySaxURISearch iSearch = new CapabilityXQuerySaxURISearch(capabilitiesFolder);
                ArrayList<CapabilitySearchReturn> capabilities =
                        iSearch.doSearchCapability(appModel.getSignerInfo().getSignerID(),
                        resourceID.toString(), appModel.getAbstractOperationName());
                CapabilitySearchReturn capability = getPrimaryFoundedCapability(capabilities);

                LOGGER.log(Level.INFO, "Using Capability - ID: {0}", capability.getCapabilityID());
                LOGGER.log(Level.INFO, "Using Capability - Issue Date: {0}", capability.getCapabilityIssueDate().toString());
                LOGGER.log(Level.INFO, "Using Capability - File: {0}", capability.getCapabilityFile().getAbsoluteFile());

                requestType = ensRequestFactory.createENSServiceRequest(resourceID,
                        appModel.getSignerInfo().getSignerID(),
                        appModel.getAbstractOperationName(),
                        appModel.getSessinDeadline(),
                        new FileInputStream(capability.getCapabilityFile()));
            }

            LOGGER.info("Creating ENSService Request document...");
            Document requestDoc = ensRequestFactory.getENSServiceRequestToDocument(requestType);

            LOGGER.info("Signing ENSService Request document...");
            X509DocumentSignerInfo model = new X509DocumentSignerInfo();
            model.setKeystorePath(appModel.getSignerInfo().getKeystorePath());
            model.setPrivateKeyPwd(appModel.getSignerInfo().getPrivateKeyPwd());
            model.setKeystorePwd(appModel.getSignerInfo().getKeystorePwd());
            model.setSignerID(appModel.getSignerInfo().getSignerID());
            X509CertificateKeyValues keyValues = X509DocumentSigner.getCertificateKeyValues(model);
            X509DocumentSigner.signXMLElement(requestDoc.getDocumentElement(),
                    keyValues, ENSServiceRequestFactory.SIGN_DOCUMENT_AFTER_NODE);
            XMLPrinter.logDocumentElement(
                    requestDoc.getDocumentElement(), "The Created ENS Service Request.", false);
            return requestDoc;
        } catch (FileNotFoundException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        } catch (URISyntaxException e) {
            throw new AbstractApplicationException(e.getMessage(), e);
        } catch (ENSServiceFactoryException e) {
            throw new AbstractApplicationException(e.getMessage(), e);
        } catch (GeneralSecurityException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        }
    }

    private CapabilitySearchReturn getPrimaryFoundedCapability(ArrayList<CapabilitySearchReturn> capabilities) throws AbstractApplicationException {
        if (capabilities.isEmpty()) {
            throw new AbstractApplicationException("No Capability founded into the specified folder");
        }
        Collections.sort(capabilities, new Comparator<CapabilitySearchReturn>() {
            public int compare(CapabilitySearchReturn o1, CapabilitySearchReturn o2) {
                XMLGregorianCalendar issueDate1 = o1.getCapabilityIssueDateToXmlGregorianCalendar();
                XMLGregorianCalendar isssueDate2 = o2.getCapabilityIssueDateToXmlGregorianCalendar();
                return issueDate1.compare(isssueDate2);
            }
        });
        return capabilities.get(0);
    }

    private String sendRPCClientRequest(Document signedRequest)
            throws AbstractApplicationException {

        ByteArrayOutputStream xmlos = new ByteArrayOutputStream();
        try {
            DOMSource source = new DOMSource(signedRequest);
            StreamResult result = new StreamResult(xmlos);
            TransformerFactory transFactory = TransformerFactory.newInstance();
            Transformer transformer = transFactory.newTransformer();
            transformer.transform(source, result);

            LOGGER.log(Level.INFO, "Writted data lenght: {0}", xmlos.size());

            LOGGER.log(Level.INFO, "Creaing RPC Client...");
            RpcClient rpcClient = new RpcClient(rabbitChannel, "",
                    appModel.getGuestENSAuthServiceMBX());

            LOGGER.log(Level.INFO, "Waiting for RPC response...");
            return rpcClient.stringCall(xmlos.toString());

        } catch (TransformerException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        } catch (IOException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        } catch (ShutdownSignalException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        } catch (TimeoutException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        }
    }

    private void closeRabbitMQGuestConnection() throws AbstractApplicationException {
        try {
            LOGGER.info("Closing RabbitMQ channel...");
            if (rabbitChannel != null && rabbitChannel.isOpen()) {
                rabbitChannel.close();
            }
            LOGGER.info("Closing RabbitMQ connection...");
            if (rabbitConnection != null && rabbitConnection.isOpen()) {
                rabbitConnection.close();
            }
        } catch (IOException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        }
    }

    private ResponseType onRPCClientResponse(String response) throws AbstractApplicationException {
        if(response.equalsIgnoreCase("INTERNAL_ERROR")){
            throw new AbstractApplicationException("Server occurred internal error");
        }
        LOGGER.info("Response: \n" + response);
        try {
            ResponseType responseType = null;
            ByteArrayInputStream inputStream = new ByteArrayInputStream(response.getBytes());

            LOGGER.info("Creating ENSService Response object...");
            responseType =
                    ensResponseFactory.getENSServiceResponseFromInputStream(inputStream);

            LOGGER.info("Creating ENSService Response document...");
            Document responseDocument =
                    ensResponseFactory.getFragmentENSServiceResponseToDocument(responseType);

            XMLPrinter.logDocumentElement(
                    responseDocument.getDocumentElement(), "Received ENS Response document", false);

            LOGGER.info("Verifying ENSService Response document sign...");
            X509CertificateSubjectInfo subjectInfo = new X509CertificateSubjectInfo();
            boolean signVerified = X509DocumentSigner.verifyXMLElementSign(
                    responseDocument.getDocumentElement(), subjectInfo);

            if (signVerified) {
                LOGGER.info("ENSService Response verified correctly");
                LOGGER.info("*******NSService Response - Signer INFO ***********");
                LOGGER.log(Level.INFO, "X509 Issuer Name: {0}", subjectInfo.getX509IssuerName());
                LOGGER.log(Level.INFO, "X509 Subject Name: {0}", subjectInfo.getX509SubjectName());
                LOGGER.log(Level.INFO, "X509 Serial Number: {0}", subjectInfo.getX509SerialNumber());
                LOGGER.info("****************************************************");
            } else {
                LOGGER.warning("NSService Response not verified!");
            }
            return responseType;
        } catch (GeneralSecurityException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        } catch (ENSServiceFactoryException ex) {
            throw new AbstractApplicationException(ex.getMessage(), ex);
        }
    }

    public synchronized boolean isGuestSubscriberListening() {
        return this.isGuestListening;
    }
}
